package io.adrainty.bolee.file.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.file.FileNameUtil;
import cn.hutool.core.text.StrPool;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.adrainty.bolee.file.adapter.FileStorageAdapter;
import io.adrainty.bolee.file.models.constants.FileConstants;
import io.adrainty.bolee.file.models.entity.FileEntity;
import io.adrainty.bolee.file.models.entity.FilePartEntity;
import io.adrainty.bolee.file.models.request.UploadMultipartFileDTO;
import io.adrainty.bolee.file.models.valobj.FileUploadStatus;
import io.adrainty.bolee.file.repository.IFileRepository;
import io.adrainty.bolee.file.service.IFileService;
import io.adrainty.bolee.framework.commons.basic.ResponseCode;
import io.adrainty.bolee.framework.commons.exception.AppException;
import io.adrainty.bolee.framework.commons.utils.JsonUtil;
import io.adrainty.bolee.framework.mq.basic.MQMessage;
import io.adrainty.bolee.framework.mq.basic.MQMessageType;
import io.adrainty.bolee.framework.mq.utils.RabbitMQUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.function.StreamBridge;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author AdRainty
 * @version V1.0.0
 * @since 2025/8/17 下午11:48
 */

@Slf4j
@Service
public class FileServiceImpl implements IFileService {

    @Resource
    private IFileRepository fileRepository;

    @Resource
    private IdentifierGenerator identifierGenerator;

    @Resource
    private FileStorageAdapter fileStorageAdapter;

    @Resource
    private StreamBridge streamBridge;

    @Override
    public IPage<FileEntity> pageFile(Integer pageNum, Integer pageSize, FileEntity param) {
        IPage<FileEntity> pageResult = fileRepository.pageFile(pageNum, pageSize, param);
        fillFilePath(pageResult.getRecords());
        return pageResult;
    }

    @Override
    @Transactional
    public FileEntity upload(UploadMultipartFileDTO multipartFileDTO, FileEntity param) {
        String newFileName = identifierGenerator.nextUUID(param) + StrPool.DASHED + multipartFileDTO.getOriginalFilename();
        param.setFileName(newFileName);
        param.setSuffix(FileNameUtil.getSuffix(newFileName));

        try (ByteArrayInputStream bis = new ByteArrayInputStream(multipartFileDTO.getFileByte())) {
            String pathUrl = fileStorageAdapter.uploadFile(param, bis);
            param.setStatus(FileUploadStatus.SUCCEED);
            param.setPathUrl(pathUrl);
            param = fileRepository.saveFileBO(param);
            if (param == null) throw new AppException(ResponseCode.FILE_SAVE_ERROR);

            // 补全完整路径
            fillFilePath(List.of(param));
        } catch (IOException e) {
            log.error("文件读取异常", e);
            throw new AppException(ResponseCode.FILE_READ_ERROR);
        }

        // 发送延迟信息:上传如果超过10分钟不进行文件业务绑定则会被消息队列清空
        afterUploadSuccess(param);

        return param;
    }

    @Override
    @Transactional
    public FileEntity initiateMultipartUpload(FileEntity fileVO) {
        // 文件重命名
        String filename = identifierGenerator.nextId(fileVO) + "-" + fileVO.getFileName();
        fileVO.setFileName(filename);
        // 文件后缀名
        String suffix = fileVO.getFileName().substring(fileVO.getFileName().lastIndexOf("."));
        fileVO.setSuffix(suffix);
        // 分片上传-初始化
        FileEntity file = fileStorageAdapter.initiateMultipartUpload(fileVO);
        // 保存数据库
        file.setBusinessType(fileVO.getBusinessType());
        file.setSuffix(suffix);
        file.setStoreFlag(fileVO.getStoreFlag());
        file.setMd5(fileVO.getMd5());
        file.setCompanyNo(fileVO.getCompanyNo());
        file.setStatus(FileUploadStatus.SENDING);

        fileVO = fileRepository.saveFileBO(file);
        // 补全完整路径
        fillFilePath(List.of(fileVO));
        afterUploadSuccess(fileVO);
        return fileVO;
    }

    @Override
    @Transactional
    public String uploadPart(UploadMultipartFileDTO uploadMultipartFile, FilePartEntity filePartVO) {
        String partETagString = fileStorageAdapter.uploadPart(filePartVO, new ByteArrayInputStream(uploadMultipartFile.getFileByte()));
        filePartVO.setUploadResult(partETagString);
        fileRepository.saveFilePart(filePartVO);
        return partETagString;
    }

    @Override
    @Transactional
    public String completeMultipartUpload(FileEntity fileVO) {
        fileRepository.deleteFilePartByUpLoadId(fileVO.getUploadId());
        fileVO.setStatus(FileUploadStatus.SUCCEED);
        fileRepository.updateUpdateStatus(fileVO);
        return fileStorageAdapter.completeMultipartUpload(fileVO);
    }

    @Override
    @Transactional
    public Boolean clearFile() {
        List<FileEntity> fileEntities = fileRepository.getNeedClearFileList();
        if (CollUtil.isEmpty(fileEntities)) return Boolean.TRUE;
        List<Long> fileListIds = fileEntities.stream().map(FileEntity::getId).toList();
        fileRepository.deleteBatchFile(fileListIds);

        for (FileEntity fileEntity : fileEntities) {
            fileStorageAdapter.delete(fileEntity.getStoreFlag().getCode(), fileEntity.getBucketName(), fileEntity.getPathUrl());
        }

        return Boolean.TRUE;
    }

    @Override
    @Transactional
    public Boolean clearFileById(Long fileId) {
        FileEntity file = fileRepository.getFile(fileId);
        if (file == null) return Boolean.TRUE;
        LocalDateTime delayTime = file.getCreatedTime().plusMinutes(FileConstants.FILE_DELAY_TIME);
        if (LocalDateTime.now().isAfter(delayTime)) {
            fileRepository.deleteFilePartByUpLoadId(file.getUploadId());
        }
        return Boolean.TRUE;
    }

    @Override
    @Transactional
    public Boolean replaceBindBatchFile(List<FileEntity> fileVOs) {
        if (CollUtil.isEmpty(fileVOs)) return Boolean.TRUE;
        Long businessId = fileVOs.get(0).getBusinessId();
        List<FileEntity> oldFileList = fileRepository.getFileListByBusinessId(businessId);
        List<Long> oldIdList = oldFileList.stream().map(FileEntity::getId).toList();
        List<Long> newIdList = fileVOs.stream().map(FileEntity::getId).toList();

        // 计算集合的单差集，即只返回【集合1】中有，但是【集合2】中没有的元素
        Set<Long> delIdList = new HashSet<>(CollUtil.subtract(oldIdList, newIdList));
        Set<Long> addIdList = new HashSet<>(CollUtil.subtract(newIdList, oldIdList));
        List<FileEntity> bindFileList = fileVOs.stream().filter(fileVO -> addIdList.contains(fileVO.getId())).toList();

        Boolean resultDTO = deleteFileInIdList(delIdList);
        if (Boolean.FALSE.equals(resultDTO)) return false;
        bindBatchFile(bindFileList);
        return Boolean.TRUE;
    }

    @Override
    public List<FileEntity> bindBatchFile(List<FileEntity> bindFileList) {
        if (CollUtil.isEmpty(bindFileList)) return new ArrayList<>();
        fileRepository.updateBindFile(bindFileList);
        List<FileEntity> fileEntityList = fileRepository.getFileListByBusinessId(bindFileList.get(0).getBusinessId());
        fillFilePath(fileEntityList);
        return fileEntityList;
    }

    @Override
    @Transactional
    public Boolean deleteFileInIdList(Collection<Long> delIdList) {
        if (CollUtil.isEmpty(delIdList)) return Boolean.TRUE;
        List<FileEntity> fileEntities = fileRepository.getFileListById(delIdList);

        List<Long> fileListIds = fileEntities.stream().map(FileEntity::getId).toList();
        fileRepository.deleteBatchFile(fileListIds);

        for (FileEntity fileEntity : fileEntities) {
            fileStorageAdapter.delete(fileEntity.getStoreFlag().getCode(), fileEntity.getBucketName(), fileEntity.getPathUrl());
        }

        return Boolean.TRUE;
    }

    @Override
    public List<FileEntity> findInBusinessIds(List<Long> businessIds) {
        List<FileEntity> fileEntities = fileRepository.getFileListByBusinessIds(businessIds);
        fillFilePath(fileEntities);
        return fileEntities;
    }

    @Override
    public Boolean deleteFileInBusinessIds(List<Long> businessIds) {
        List<FileEntity> fileEntities = fileRepository.getFileListByBusinessIds(businessIds);
        List<Long> fileListIds = fileEntities.stream().map(FileEntity::getId).toList();
        fileRepository.deleteBatchFile(fileListIds);

        for (FileEntity fileEntity : fileEntities) {
            fileStorageAdapter.delete(fileEntity.getStoreFlag().getCode(), fileEntity.getBucketName(), fileEntity.getPathUrl());
        }

        return Boolean.TRUE;
    }

    private void afterUploadSuccess(FileEntity fileVO) {
        // 发送延迟信息:上传如果超过10分钟不进行文件业务绑定则会被消息队列清空
        Long messageId = (Long) identifierGenerator.nextId(fileVO);
        MQMessage mqMessage = MQMessage.builder()
                .messageId(messageId)
                .content(JsonUtil.toJson(fileVO))
                .messageType(MQMessageType.FILE_MESSAGE.name())
                .createdTime(LocalDateTime.now())
                .sender("system")
                .build();
        Message<MQMessage> message = MessageBuilder.withPayload(mqMessage)
                .setHeader("x-delay", FileConstants.FILE_DELAY_TIME).build();
        streamBridge.send(RabbitMQUtil.getProducerChannelName(MQMessageType.FILE_MESSAGE), message);
    }

    /**
     * 填充文件路径
     * TODO 考虑针对文件ID做缓存, 避免多次调用Aliyun接口
     * 缓存时间为commonProperties.getTempFileReadTimeout - 随机时长
     *
     * @param fileEntities 文件列表
     */
    private void fillFilePath(List<FileEntity> fileEntities) {
        if (CollUtil.isEmpty(fileEntities)) return;
        for (FileEntity fileEntity : fileEntities) {
            String readablePath = fileStorageAdapter.getReadablePath(
                    fileEntity.getStoreFlag().getCode(), fileEntity.getBucketName(), fileEntity.getPathUrl());
            fileEntity.setPathUrl(readablePath);
        }
    }

}
